package com.avego.game;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

/**
 * The Class Board. represent a game Board
 */
public class Board
{
	private Map<Player, BoardCoordinates> playersCoordinates;
	private Referee mReferee;
	private Player[][] mBoard;
	private int numRows, numColumns;
	private Random random = new Random();

	public Board()
	{}

	public Board(int rows, int columns)
	{
		numRows = rows;
		numColumns = columns;
		playersCoordinates = new HashMap<Player, BoardCoordinates>();
		mBoard = new Player[rows][columns];
	}

	/**
	 * Adds a player in a random coordinate.
	 *
	 * @param player the player
	 * @return the player board coordinates
	 */
	public BoardCoordinates addPlayer(Player player)
	{
		boolean flag = true;

		int row = 0;
		int column = 0;

		// TODO: enforce a initial valid distribution among players
		while (flag)
		{
			row = random.nextInt(numRows);
			column = random.nextInt(numColumns);

			if (mBoard[row][column] == null)
			{
				mBoard[row][column] = player;
				flag = false;
			}
		}

		BoardCoordinates coord = new BoardCoordinates(row, column);
		playersCoordinates.put(player, coord);
		return coord;
	}

	/**
	 * Move a player in a board.
	 *
	 * @param player the player
	 * @param moveTo the coordinates to move the player
	 */
	public void movePlayer(Player player, BoardCoordinates moveTo)
	{
		movePlayerToNewPosition(player, moveTo);

		mReferee.validatePlayerMove(player);
	}

	private void movePlayerToNewPosition(Player player, BoardCoordinates moveTo)
	{
		BoardCoordinates currentPosition = playersCoordinates.get(player);
		mBoard[currentPosition.getRow()][currentPosition.getColumn()] = null;

		currentPosition.setColumn(moveTo.getColumn());
		currentPosition.setRow(moveTo.getRow());

		mBoard[currentPosition.getRow()][currentPosition.getColumn()] = player;
	}

	/**
	 * Check is there is a single player in game.
	 *
	 * @return true, if there is a winner player. Also updates the winning player as the winner
	 */
	public boolean checkForWinner()
	{
		if (playersCoordinates.size() == 1)
		{
			for (Player player : playersCoordinates.keySet())
			{
				player.win();
				return true;
			}
		}
		return false;
	}

	/**
	 * Gets the available moves for a player.
	 *
	 * @param player the player
	 * @return the available moves
	 */
	public List<BoardCoordinates> getAvailableMoves(Player player)
	{
		BoardCoordinates position = playersCoordinates.get(player);
		List<BoardCoordinates> availableMovments = new ArrayList<BoardCoordinates>();
		int rowMovement, columnMovement;

		for (int rowIndex = -1; rowIndex <= 1; rowIndex++)
		{
			for (int columnIndex = -1; columnIndex <= 1; columnIndex++)
			{

				if (rowIndex == 0 && columnIndex == 0)
					continue;

				rowMovement = position.getRow() + rowIndex;

				if (rowMovement < 0 || rowMovement == numRows)
					continue; // check if the possible move is out of the board

				columnMovement = position.getColumn() + columnIndex;
				if (columnMovement < 0 || columnMovement == numColumns)
					continue;// check if the possible move is out of the board

				if (mBoard[rowMovement][columnMovement] != null)
					continue; // player cannot move to the same position as
								// another player.
								// actually this should not happen..

				availableMovments.add(new BoardCoordinates(rowMovement,
						columnMovement));

			}
		}

		return availableMovments;
	}

	/**
	 * Checks for other players in the radius of a player.
	 *
	 * @param player the player
	 * @param radius the radius constrain
	 * @return true, if exists other players on that radius
	 */
	public boolean hasPlayersOnRadius(Player player, int radius)
	{
		int rowMovement, columnMovement;

		BoardCoordinates playerPosition = playersCoordinates.get(player);

		for (int rowIndex = -radius; rowIndex <= radius; rowIndex++)
		{
			for (int columnIndex = -radius; columnIndex <= radius; columnIndex++)
			{
				if (rowIndex == 0 && columnIndex == 0)
					continue; // ignore center position

				rowMovement = playerPosition.getRow() + rowIndex;
				if (rowMovement < 0 || rowMovement >= numRows)
					continue; // row point out the board

				columnMovement = playerPosition.getColumn() + columnIndex;
				if (columnMovement < 0 || columnMovement >= numColumns)
					continue;// column point out the board

				if (mBoard[rowMovement][columnMovement] != null)
					return true;
			}
		}

		return false;
	}

	/**
	 * Removes the player from board but let the player in the game.
	 *
	 * @param player the player
	 */
	public void removePlayerFromBoard(Player player)
	{
		BoardCoordinates position = playersCoordinates.get(player);
		mBoard[position.getRow()][position.getColumn()] = null;
	}

	/**
	 * Removes the player from game.
	 *
	 * @param player the player
	 */
	public void removePlayerFromGame(Player player)
	{
		BoardCoordinates position = playersCoordinates.get(player);
		mBoard[position.getRow()][position.getColumn()] = null;

		playersCoordinates.remove(player);
	}

	/**
	 * Count players on board, by checking the internal matrix game.
	 *
	 * @return the number of players in the board
	 */
	public int countPlayersOnBoard()
	{
		int count = 0;

		for (int r = 0; r < numRows; r++)
		{
			for (int c = 0; c < numColumns; c++)
			{
				if (mBoard[r][c] != null)
					count++;
			}
		}

		return count;

	}

	public void setReferee(Referee referee)
	{
		mReferee = referee;
	}

	/**
	 * Gets the player in the board matrix coordinates.
	 *
	 * @param c the player coordinates
	 * @return the player
	 */
	public Player getPlayerInCoords(BoardCoordinates c)
	{
		return mBoard[c.getRow()][c.getColumn()];
	}

	/**
	 * Gets the player coordinates.
	 *
	 * @param player the player
	 * @return the player coordinates
	 */
	public BoardCoordinates getPlayerCoordinates(Player player)
	{
		return playersCoordinates.get(player);
	}

	/**
	 * Count the numbers of players on game.
	 *
	 * @return the number of players in the game
	 */
	public int countPlayersOnGame()
	{
		return playersCoordinates.size();
	}
}
